
package carmadness.model;

import carmadness.GameState;
import carmadness.Game;

import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;

/**
 * A világot reprezentáló osztály. Gondoskodik az út hálózat felépítéséről,
 * nyilvántartja annak elemeit, és közvetíti az órajelet ezen elemekhez.
 * 
 * @author Szelei
 * @version 1.0
 * @created 10-Apr-2010 2:32:44 PM
 */
public class World implements ISteppable
{
	/**
	 * A bankra mutató referencia
	 */
	private Bank bank;
	/**
	 * A játékosra mutató referencia.
	 */
	private Robber player;
	/**
	 * A rendőrre mutató referencia.
	 */
	private Police police;
	/**
	 * Rendőrörsre mutató referencia.
	 */
	private PoliceStation policeStation;
	/**
	 * Az összes útelemet tároló lista.
	 */
	private List<RoadSegment> roadSegments = new LinkedList<RoadSegment>();
	/**
	 * A világban található források listája.
	 */
	private List<SourceRoadSegment> sources = new LinkedList<SourceRoadSegment>();
	/**
	 * A léptethető elemeket tároló attribútum.
	 */
	private List<ISteppable> steppables = new LinkedList<ISteppable>();
	/**
	 * Egy step iterációban törölt elemek tárolója. (Konkurens hozzáférés
	 * kezelésére.)
	 */
	private List<ISteppable> steppablesToRemove = new LinkedList<ISteppable>();
	/**
	 * A világ kiterjedése vízszintes irányban (mezők száma).
	 */
	private int dimension_x;
	/**
	 * A világ kiterjedése függőleges irányban (mezők száma).
	 */
	private int dimension_y;
	/**
	 * Az egyetlen World példány.
	 */
	private static World instance;

	private World()
	{

	}

	/**
	 * Visszaadja az egyetlen World példányt.
	 */
	public static World getInstance()
	{
		if ( instance == null )
		{
			instance = new World();
		}
		return instance;
	}

	/**
	 * A játék kudarccal végződött.
	 */
	public void loseGame()
	{
		Game.getInstance().setState( GameState.Lose );
	}

	/**
	 * A játék kudarccal végződött. Külön metódus arra az esetre, ha szükség
	 * volna arra, hogy melyik objektum miatt lett vége.
	 * 
	 * @param initiator
	 *            kezdeményező objektum.
	 */
	public void loseGame( Object initiator )
	{
		loseGame();
	}

	/**
	 * Felveszi a paraméterként kapott objektumot a steppables listára.
	 * 
	 * @param steppable
	 *            IStepable interfészt megvalósító objektum.
	 */
	public void registerSteppable( ISteppable steppable )
	{
		steppables.add( steppable );
	}

	public void unregisterSteppable( ISteppable steppable )
	{
		steppablesToRemove.add( steppable );
	}

	/**
	 * Órajelütésre lépteti a benne lévő órajelre léptethető objektumokat.
	 */
	public void step()
	{
		Iterator<ISteppable> itr = steppables.iterator();

		while ( itr.hasNext() )
		{
			itr.next().step();
		}

		// Amennyiben eltávolítottak eleme(ke)t az iteráció során...
		if ( steppablesToRemove.size() > 0 )
		{
			steppables.removeAll( steppablesToRemove );
			steppablesToRemove.clear();
		}
	}

	/**
	 * Rendőr "elpusztításáról" gondoskodik.
	 * 
	 * @param vehicle
	 *            Rendőr típusú jármű.
	 */
	public void visitVehicle( Police vehicle )
	{
		vehicle.kill();
	}

	/**
	 * Civil "elpusztításáról" gondoskodik.
	 * 
	 * @param vehicle
	 *            Civil típusú jármű.
	 */
	public void visitVehicle( Civilian vehicle )
	{
		vehicle.kill();
	}

	/**
	 * Rabló "elpusztításáról" gondoskodik, kezeli a játék vége eseményt.
	 * 
	 * @param vehicle
	 *            Rabló típusú jármű.
	 */
	public void visitVehicle( Robber vehicle )
	{
		vehicle.kill();
	}

	/**
	 * A játék sikerrel végződött.
	 */
	public void winGame()
	{
		Game.getInstance().setState( GameState.Win );
	}

	/**
	 * Visszatér a Bank útelemmel.
	 * 
	 * @return Bank objektum.
	 */
	public Bank getBank()
	{
		return bank;
	}

	/**
	 * Beállítja a player referenciát.
	 * 
	 * @param player
	 *            Player objektum.
	 */
	public void setPlayer( Robber player )
	{
		this.player = player;
	}

	/**
	 * Beállítja a police referenciát.
	 * 
	 * @param police
	 *            Police objektum.
	 */
	public void setPolice( Police police )
	{
		this.police = police;
	}

	/**
	 * Visszatér a policeStation referenciával.
	 * 
	 * @return PoliceStation objektum.
	 */
	public PoliceStation getPoliceStation()
	{
		return policeStation;
	}

	/**
	 * Visszatér a pálya X irányú kiterjedésével.
	 * 
	 * @return Szélesség
	 */
	public int getDimension_x()
	{
		return dimension_x;
	}

	/**
	 * Visszatér a pálya Y irányú kiterjedésével.
	 * 
	 * @return Magasság
	 */
	public int getDimension_y()
	{
		return dimension_y;
	}

	/**
	 * Beállítja a pálya X irányú kiterjedését.
	 * 
	 * @param value
	 *            Szélesség
	 */
	public void setDimension_x( int value )
	{
		dimension_x = value;
	}

	/**
	 * Beállítja a pálya Y irányú kiterjedését.
	 * 
	 * @param value
	 *            Magasság
	 */
	public void setDimension_y( int value )
	{
		dimension_y = value;
	}

	/**
	 * Megmondja, hogy a fromRS-hez képest a toRS milyen irányba esik.
	 * 
	 * @param fromRS
	 *            Viszonyítási pontot képviselő útelem.
	 * @param toRS
	 *            Az útelem aminek a relatív irányát keressük.
	 * @return Az irány szöge.
	 */
	public float getAngleByPlacement( RoadSegment fromRS, RoadSegment toRS )
	{
		int fromIndex = roadSegments.indexOf( fromRS );
		int toIndex = roadSegments.indexOf( toRS );

		switch ( fromIndex % dimension_x - toIndex % dimension_x )
		{
		case 0: // Egymás alatt vannak.
			return (float)( Math.PI * ( fromIndex < toIndex ? 1.5d : 0.5d ) );
		case 1: // Balra esik.
			return (float)Math.PI;
		case -1: // Jobbra esik.
			return 0f;

		default: // Nem szomszédosak.
			// TODO: Itt illene kivételt dobni.
			return 0f;
		}
	}

	/**
	 * Felveszi a paraméterül kapott útelemet a roadSegments listába.
	 * 
	 * @param segment
	 *            Új útelem pldány.
	 */
	public void addRoadSegment( RoadSegment segment )
	{
		roadSegments.add( segment );
	}

	/**
	 * Felveszi a paraméterül kapott Bank típusú útelemet a roadSegments
	 * listába, illetve beállítja a bank referenciát.
	 * 
	 * @param segment
	 *            Bank pldány.
	 */
	public void addRoadSegment( Bank segment )
	{
		addRoadSegment( (RoadSegment)segment );
		bank = segment;
	}

	/**
	 * Felveszi a paraméterül kapott PoliceStation típusú útelemet a
	 * roadSegments listába, illetve beállítja a policeStation referenciát.
	 * 
	 * @param segment
	 *            PoliceStation pldány.
	 */
	public void addRoadSegment( PoliceStation segment )
	{
		addRoadSegment( (RoadSegment)segment );
		policeStation = segment;
	}

	/**
	 * Felveszi a paraméterül kapott SourceRoadSegment típusú útelemet a
	 * roadSegments illetve a surces listába.
	 * 
	 * @param segment
	 *            SourceRoadSegment példány.
	 */
	public void addRoadSegment( SourceRoadSegment segment )
	{
		addRoadSegment( (RoadSegment)segment );
		sources.add( segment );
	}
}
